/**
 * Copyright 2018-2019 jianggujin (www.jianggujin.com).
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.jianggujin.modulelink.support.plugin;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.jianggujin.modulelink.exception.JModuleLinkException;
import com.jianggujin.modulelink.support.annotation.JIntercepts;
import com.jianggujin.modulelink.support.annotation.JSignature;

/**
 * @author jianggujin
 *
 */
public class JPlugin implements InvocationHandler {

    private final Object target;
    private final JInterceptor interceptor;
    private final Map<Class<?>, Set<Method>> signatureMap;

    private JPlugin(Object target, JInterceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
        this.target = target;
        this.interceptor = interceptor;
        this.signatureMap = signatureMap;
    }

    public static Object wrap(Object target, JInterceptor interceptor) throws JModuleLinkException {
        Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
        Class<?> type = target.getClass();
        Class<?>[] interfaces = getAllInterfaces(type);
        return Proxy.newProxyInstance(type.getClassLoader(), interfaces,
                new JPlugin(target, interceptor, signatureMap));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Set<Method> methods = signatureMap.get(method.getDeclaringClass());
        if (methods != null && methods.contains(method)) {
            return interceptor.intercept(new JInvocation(target, method, args));
        }
        return method.invoke(target, args);
    }

    private static Map<Class<?>, Set<Method>> getSignatureMap(JInterceptor interceptor) throws JModuleLinkException {
        JIntercepts interceptsAnnotation = interceptor.getClass().getAnnotation(JIntercepts.class);
        if (interceptsAnnotation == null) {
            throw new JModuleLinkException(
                    "No @JIntercepts annotation was found in interceptor " + interceptor.getClass().getName());
        }
        JSignature[] signatures = interceptsAnnotation.value();
        Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
        for (JSignature signature : signatures) {
            Class<?> type = null;
            if (signature.typeName().length() > 0) {
                try {
                    type = interceptor.getClass().getClassLoader().loadClass(signature.typeName());
                } catch (ClassNotFoundException e) {
                    throw new JModuleLinkException(
                            "No type class was found in interceptor " + interceptor.getClass().getName());
                }
            } else if (!Void.class.equals(signature.type())) {
                type = signature.type();
            } else {
                throw new JModuleLinkException("No type was found in interceptor " + interceptor.getClass().getName());
            }

            Set<Method> methods = signatureMap.get(type);
            if (methods == null) {
                methods = new HashSet<Method>();
                signatureMap.put(type, methods);
            }
            // 方法名为空，拦截所有方法
            if (signature.method().length() == 0) {
                Method[] tmp = type.getMethods();
                if (tmp != null) {
                    for (Method method : tmp) {
                        methods.add(method);
                    }
                }
            } else {
                try {
                    methods.add(type.getMethod(signature.method(), signature.args()));
                } catch (NoSuchMethodException e) {
                    throw new JModuleLinkException(
                            "Could not find method on " + type + " named " + signature.method() + ". Cause: " + e, e);
                }
            }
        }
        return signatureMap;
    }

    /**
     * 获得所有接口
     * 
     * @param type
     * @return
     */
    public static Class<?>[] getAllInterfaces(Class<?> type) {
        Set<Class<?>> interfaces = new HashSet<Class<?>>();
        while (type != null) {
            for (Class<?> c : type.getInterfaces()) {
                interfaces.add(c);
            }
            type = type.getSuperclass();
        }
        return interfaces.toArray(new Class<?>[interfaces.size()]);
    }

}